/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/
/*
Authors: Ram Ovadia 201374022 and Lior Fass 300069705
Project: HW Assignment 4 
Description: This is the module implementing the client functionality of Oiway.  
*/
/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/

#include "Client.h"

Client_attr *client_init(char *server_address, int xi, int yi, int xj, int yj, char *client_log_file_name);

void cleanup(Client_attr *client);

int gps_connect(Client_attr *client);

int server_connect(Client_attr *client);

void navigate(Client_attr *client);

int plan_route(Client_attr *client);

void writeToLog(FILE *file, char *buf);

void print_route(Client_attr *client);

/*
 * Initiates a Client_attr struct. Allocates space and initiates variables.    
 * 
 * Input:
 * -----
 * server_address - pointer to string holding valid ip address of Oiway server and gps.  
 * xi, yi / xj, yj - coordinates of initial / final node.
 * client_log_file_name - pointer to string holding name of log file to be created by the client.    
 *
 * Output:
 * -----
 * Return Value:
 * pointer to Client_attr struct if successful or null. opens log file, initiates winsock.
 */ 
Client_attr *client_init(char *server_address, int xi, int yi, int xj, int yj, char *client_log_file_name)
{
	Client_attr *client = NULL;

	client = (Client_attr *)malloc(sizeof(Client_attr));
	if (client == NULL)
	{
		printf("Failed to allocate mem for client attributes\r\n");
		return NULL;
	}
	client->server_addr = server_address;
	client->s_gps = INVALID_SOCKET;
	client->s_server = INVALID_SOCKET;
	client->map = NULL;
	client->route = NULL;
	client->src.x = xi;
	client->src.y = yi;
	client->src.ind = -1;
	client->dest.x = xj;
	client->dest.y = yj;
	client->dest.ind = -1;
	client->log_file = fopen(client_log_file_name, "wb");
	if (client->log_file == NULL)
	{
		printf("Failed to open log file for client\r\n");
		cleanup(client);
		return NULL;
	}
	// init winsock
	if ( winsock_init() != 0 )
	{
		printf("Failed to initialize winsock\r\n");
		cleanup(client);
		return NULL;
	}
	return client;
}

/*
 * Cleans a Client_attr struct. De-allocates space, frees variables, closes sockets and log files. Cleans winsock.    
 * 
 * Input:
 * -----
 * client - pointer to Client_attr struct to be cleaned.    
 *
 * Output:
 * -----
 * void
 */ 
void cleanup(Client_attr *client)
{
	if (client!=NULL)
	{
		if (client->s_gps != INVALID_SOCKET)
			closesocket(client->s_gps);
		if (client->s_server != INVALID_SOCKET)
			closesocket(client->s_server);
		if (client->log_file != NULL)
			fclose(client->log_file);
		if (client->map != NULL)
			map_clean(client->map);
		if (client->route != NULL)
			free(client->route);
		free(client);
	}
	WSACleanup();
}

/*
 * Creates TCP socket and connects.   
 * 
 * Input:
 * -----
 * s - pointer to uninitialized socket.     
 * addr - pointer to string holding the ip address of the server.
 * port - int holding port number to connect to in the server. 
 *
 * Output:
 * -----
 * 0 - successful. Socket s connected to addr, port.
 * 1 - failed. Socket s is undefined. 
 */ 
int socket_create_connect(SOCKET *s, char *addr, int port)
{
	struct sockaddr_in server;

	if((*s = socket(AF_INET , SOCK_STREAM , IPPROTO_TCP )) == INVALID_SOCKET)
    {
        printf("Could not create socket : %d\r\n" , WSAGetLastError());
		return 1;
    }
	//DEBUG:
    printf("Socket created.\r\n");
	
	server.sin_addr.s_addr = inet_addr( addr );
    server.sin_family = AF_INET;
    server.sin_port = htons( port );
	 
    //Connect to remote server
    if (connect(*s , (struct sockaddr *)&server , sizeof(server)) < 0)
    {
		//DEBUG:
        printf("connect error\r\n");
        return 1;
    }
	//DEBUG:
	printf("Connected\r\n");
	return 0;
}

/*
 * Connects to gps.   
 * 
 * Input:
 * -----
 * client - pointer to Client_attr struct.     
 *
 * Output:
 * -----
 * 0 - successful. s_gps socket on client is connected to the gps. 
 * 1 - failed. 
 */ 
int gps_connect(Client_attr *client)
{
	if ( socket_create_connect(&(client->s_gps), client->server_addr, GPS_PORT) != 0 )
	{
		writeToLog(client->log_file, "Failed to connect to GPS\r\n");
		return 1;
	}
	writeToLog(client->log_file, "Successfully logged into GPS\r\n");
	return 0;
}

/*
 * Connects to server.   
 * 
 * Input:
 * -----
 * client - pointer to Client_attr struct.     
 *
 * Output:
 * -----
 * 0 - successful. s_server socket on client is connected to the server. 
 * 1 - failed. 
 */ 
int server_connect(Client_attr *client)
{
	if ( socket_create_connect(&(client->s_server), client->server_addr, SERVER_PORT) != 0 )
	{
		writeToLog(client->log_file, "Failed to connect to server\r\n");
		return 1;
	}
	writeToLog(client->log_file, "Successfully logged into server\r\n");
	return 0;
}

/*
 * Navigates client from initial point to destination. 
 * assumes connections to server and gps have been made (has map).
 * and that shortest route has been computed. All data is maintained on client struct. 
 * 
 * Input:
 * -----
 * client - pointer to Client_attr struct.     
 *
 * Output:
 * -----
 * Traverses the calculated route according to signals from gps. 
 * If connection to server is available, updates server on edge length.  
 */
void navigate(Client_attr *client)
{
	char *gps_reply = NULL;
	BOOL standalone_flag = FALSE;
	BOOL dest_flag = FALSE;
	int i = 0;
	int time_ref = -1;
	int cur_node_index, last_node_index;
	Node *node = NULL;
	char string[LINE_LEN];
	
	while (!dest_flag)
	{
		cur_node_index = (client->route)[i];
		if (ReceiveString(&gps_reply, client->s_gps) != TRNS_SUCCEEDED)
		{
			writeToLog(client->log_file, "Failed to receive time from GPS\r\n");
			writeToLog(client->log_file, "Failed to reach destination\r\n");
			break;
		}
	
		node = map_i2c(client->map, cur_node_index);
		sprintf(string, "GPS time at %d %d is %d\r\n", node->x, node->y, atoi(gps_reply));
		writeToLog(client->log_file, string);

		if ( time_ref == -1 )
		{
			//first node
			time_ref = atoi(gps_reply);
		} else {
			//rest of nodes
			if (!standalone_flag)
			{
				sprintf(string, "%d %d %d ", last_node_index, cur_node_index, atoi(gps_reply)-time_ref);
				if ( SendString(string, client->s_server ) != TRNS_SUCCEEDED )
				{
					standalone_flag = TRUE;
					writeToLog(client->log_file, "Failed to update Server\r\n");
				}
			}
			time_ref = atoi(gps_reply);
		}
		last_node_index = cur_node_index;
		i++;
		if ( cur_node_index == client->dest.ind )
		{
			dest_flag = TRUE;
			writeToLog(client->log_file, "You have reached destination\n");
		}
		free(gps_reply);
		gps_reply = NULL;
	}
}

/*
 * Plans shortest root from client's src node to its dest node.
 * assumes map has been recieved from server.
 * All data is maintained on client struct. 
 * 
 * Input:
 * -----
 * client - pointer to Client_attr struct.     
 *
 * Output:
 * -----
 * 0 - successful. Client route member holds the nodes to be traversed in the order needed for the shortest path.
 * 1 - failed. 
 */
int plan_route(Client_attr *client){
	
	int *temp_route = NULL;
	if (( client == NULL ) || ( client->map == NULL ))
	{
		printf("invalid client argument\r\n");
		return 1;
	}
	client->route = (int *)malloc(sizeof(int)*client->map->n);
	if (client->route == NULL)
	{
		printf("failed to allocate route array\r\n");
		return 1;
	}
	if ( djikstra(client->map->graph, client->route, client->map->n, (client->src).ind, (client->dest).ind) != 0 )
		return 1;
	print_route(client);
	return 0;
}

/*
 * Prints to log and screen the ordered nodes in the shortest path computed. 
 * 
 *
 * Input:
 * -----
 * client - pointer to Client_attr struct.     
 *
 * Output:
 * -----
 * 
 */
void print_route(Client_attr *client)
{
	char string[LINE_LEN];
	int i,node_index;
	Node *node;
	i=0;

	if (( client == NULL ) || ( client->route == NULL ))
	{
		printf("invalid client argument\r\n");
	}
	else
	{
		node_index = client->route[i];
		sprintf(string, "Calculated path:");
		writeToLog(client->log_file, string);
		while (( node_index != -1 ) && (i < client->map->n ))
		{
			node = map_i2c(client->map, node_index);
			if ( node != NULL )
			{
				sprintf(string, " %d %d", node->x, node->y);
				writeToLog(client->log_file, string);
			}
			i++;
			node_index = client->route[i];
		}
		sprintf(string, "\r\n");
		writeToLog(client->log_file, string);
	}
}

/*
 * Tests the validy of the input coordinates  
 * Assumes map has been recieved. 
 *
 * Input:
 * -----
 * client - pointer to Client_attr struct.  
 * xi, yi / xj, yj - coordinates of initial / final node.
 *
 * Output:
 * -----
 * 0 - successful. Client has been updated with indexes and coordinates of the source and destination nodes. 
 * 1 - failed. 
 */
int test_coordinates(Client_attr *client, int xi, int yi, int xj, int yj)
{
	Node node;
	int ind;

	//test source
	node.x = xi;
	node.y = yi;
	ind = map_c2i(client->map, &node);
	if (ind == -1)
	{
		writeToLog(client->log_file, "Bad Coordinates\r\n");
		return 1;
	}
	//set source
	client->src.ind = ind;
	client->src.x = xi;
	client->src.y = yi;

	//test dest
	node.x = xj;
	node.y = yj;
	ind = map_c2i(client->map, &node);
	if (ind == -1)
	{
		writeToLog(client->log_file, "Bad Coordinates\r\n");
		return 1;
	}
	//set dest
	client->dest.ind = ind;
	client->dest.x = xj;
	client->dest.y = yj;

	return 0;
}

/*
 * Client's main. 
 *
 * Input:
 * -----
 * server_address - pointer to string holding valid ip address of Oiway server and gps.  
 * xi, yi / xj, yj - coordinates of initial / final node.
 * client_log_file_name - pointer to string holding name of log file to be created by the client.  
 *
 * Output:
 * -----
 * 0 - successful.
 * 1 - failed. 
 */
int client_main(char *server_address, int xi, int yi, int xj, int yj, char *client_log_file_name)
{

	Client_attr *client = NULL;
	char *buf;

	//init client attribues
	client = client_init(server_address, xi, yi, xj, yj, client_log_file_name);

	if (client == NULL)
	{
		cleanup(client);
		return 1;
	}

	if ( server_connect(client) != 0 )
	{
		cleanup(client);
		return 1;
	}

	buf = NULL;
	if ( ReceiveString(&buf, client->s_server) != TRNS_SUCCEEDED )
	{
		printf("Failed to recieve roadmap from server\r\n");
		writeToLog(client->log_file, "Failed to connect to server\r\n");
		cleanup(client);
		return 1;
	}
	writeToLog(client->log_file, "Received the road map from server\r\n");

	client->map = map_unmarshal(buf);

	if (client->map == NULL)
	{
		free(buf);
		cleanup(client);
		return 1;
	}

	if ( test_coordinates(client, xi, yi, xj, yj) == 0 )
	{	//plan route
		if (plan_route(client) == 0)
		{
			//connect to gps
			if (gps_connect(client) == 0)
			{
				//navigate
				navigate(client);
				free(buf);
				cleanup(client);
				return 0;
			}
		}
	}

	free(buf);
	cleanup(client);
    return 1;
}

